﻿namespace NetFx.SMIL.Definitions
{
  using System;
  using System.Text;
  using System.Collections;
  using System.Collections.Generic;

  using System.Reflection;
  using System.Reflection.Emit;
  using System.Runtime.InteropServices;

  using System.Diagnostics;
  using System.Diagnostics.CodeAnalysis;
  using System.Diagnostics.SymbolStore;
  using System.Security;
  using System.Security.Permissions;

  using NetFx.MSIL;
  using NetFx.SMIL.References;

  public class ModuleDef:ReflectDef
  {
    #region Statics
    public static ModuleDef it()
    {
      return new ModuleDef();
    }
    public static ModuleDef of(string name)
    {
      return new ModuleDef(name);
    }
    public static ModuleDef of(string name, string filename)
    {
      return new ModuleDef(name, filename);
    }
    public static ModuleDef of(string name,string filename,bool emitSymbol)
    {
      return new ModuleDef(name,filename,emitSymbol);
    }
    public static ModuleDef of(Module module)
    {
      return new ModuleDef(module);
    }
    #endregion
    #region Fields
    protected string fileName =string.Empty;
    protected bool emitSymbol = true;
    protected MethodRef entryPoint = null;
    protected List<FieldDef> fields = null;
    protected List<MethodDef> methods = null;
    protected List<TypeDef> types = null;
    #endregion
    #region Properties
    public override DefType DefinitionType
    {
      get { return DefType.Module; }
    }
    public virtual string FileName
    {
      get
      {
        return this.fileName;
      }
      set
      {
        this.fileName = value;
      }
    }
    public virtual bool EmitSymbol
    {
      get
      {
       return this.emitSymbol;
      }
      set
      {
        this.emitSymbol = value;
      }
    }
    public virtual MethodRef EntryPoint
    {
      get
      {
        return this.entryPoint;
      }
      set
      {
        this.entryPoint = value;
      }
    }
    public virtual List<FieldDef> Fields
    {
      get
      {
        return this.fields == null ? 
          this.fields = new List<FieldDef>() :
          this.fields;
      }
    }
    public virtual List<MethodDef> Methods
    {
      get
      {
        return this.methods == null ?
          this.methods = new List<MethodDef>() :
          this.methods;
      }
    }
    public virtual List<TypeDef> Types
    {
      get
      {
        return this.types == null ?
          this.types = new List<TypeDef>() :
          this.types;
      }
    }
    #endregion
    #region Constructors
    public ModuleDef():base()
    {

    }
    public ModuleDef(string name):base(name)
    {

    }
    public ModuleDef(string name, string fileName)
      : this(name)
    {
      this.fileName = fileName;
    }
    public ModuleDef(string name, string fileName,bool emitSymbol)
      : this(name,fileName)
    {
      this.emitSymbol = emitSymbol;
    }
    public ModuleDef(Module module)
      : base()
    {
      this.OnReflecting(module);
    }
    #endregion
    #region Methods
    #region Defining
    public virtual FieldDef DefineField(string name)
    {
      return this.AddField(FieldDef.of(name));
    }
    public virtual FieldDef FindField(string name)
    {
      if (!string.IsNullOrEmpty(name) && this.fields != null)
      {
        foreach (FieldDef field in this.fields)
        {
          if (field != null && field.Name == name)
          {
            return field;
          }
        }
      }
      return null;
    }
    public virtual FieldDef RemoveField(string name)
    {
      return this.RemoveField(this.FindField(name));
    }
    public virtual MethodDef DefineMethod(string name)
    {
      return this.AddMethod(MethodDef.of(name));
    }
    public virtual MethodDef FindMethod(string name)
    {
      if (!string.IsNullOrEmpty(name) && this.methods != null)
      {
        foreach (MethodDef method in this.methods)
        {
          if (method != null && method.Name == name)
          {
            return method;
          }
        }
      }
      return null;
    }
    public virtual MethodDef RemoveMethod(string name)
    {
      return this.RemoveMethod(this.FindMethod(name));
    }
    public virtual TypeDef DefineType(string name)
    {
      return this.AddType(TypeDef.of(name));
    }
    public virtual TypeDef FindType(string name)
    {
      if (!string.IsNullOrEmpty(name) && this.types != null)
      {
        foreach (TypeDef type in this.types)
        {
          if (type != null && type.Name == name)
          {
            return type;
          }
        }
      }
      return null;
    }
    public virtual TypeDef RemoveType(string name)
    {
      return this.RemoveType(this.FindType(name));
    }
    #endregion
    #region Helpers
    protected virtual FieldDef AddField(FieldDef field)
    {
      if (field != null)
      {
        field.Parent = this;
        this.Fields.Add(field);
      }
      return field;
    }
    protected virtual MethodDef AddMethod(MethodDef method)
    {
      if (method != null)
      {
        method.Parent = this;
        this.Methods.Add(method);
      }
      return method;
    }
    protected virtual TypeDef AddType(TypeDef type)
    {
      if (type != null)
      {
        type.Parent = this;
        this.Types.Add(type);
      }
      return type;
    }
    protected virtual FieldDef RemoveField(FieldDef field)
    {
      if (field != null)
      {
        field.Parent = null;
        this.Fields.Remove(field);
      }
      return field;
    }
    protected virtual MethodDef RemoveMethod(MethodDef method)
    {
      if (method != null)
      {
        method.Parent = null;
        this.Methods.Remove(method);
      }
      return method;
    }
    protected virtual TypeDef RemoveType(TypeDef type)
    {
      if (type != null)
      {
        type.Parent = null;
        this.Types.Remove(type);
      }
      return type;
    }

    protected override AnyGenerator OnDefining(AnyGenerator container)
    {
      if (container is AssemblyGenerator)
      {
        AssemblyGenerator ag = container as AssemblyGenerator;
        return (!string.IsNullOrEmpty(this.FileName))
          ? ag.DefineModule(this.Name, this.FileName, this.emitSymbol)
          : ag.DefineModule(this.Name);
      }
      return base.OnDefining(container);
    }
    protected override AnyGenerator OnCompleting(AnyGenerator generator)
    {
      if (generator is ModuleGenerator)
      {
        (generator as ModuleGenerator).EntryPoint 
          = this.EntryPoint.ReflectInfo as MethodInfo;
        this.GenerateList(generator, this.Types);
        this.GenerateList(generator, this.Fields);
        this.GenerateList(generator, this.Methods);
      }
      return generator;
    }
    protected override ReflectDef OnReflecting(object source)
    {
      Module module = source as Module;
      if (module != null)
      {
        this.name = module.Name;
        if (module is ModuleBuilder)
        {
          ModuleBuilder mb = module as ModuleBuilder;

          this.emitSymbol = (mb.GetSymWriter() != null);
        }

        this.DisposeList(true);

        this.ReloadMembers(module.GetFields(AllAccessFlags),this.Fields);
        this.ReloadMembers(module.GetMethods(AllAccessFlags), this.Methods);
        this.ReloadMembers(module.GetTypes(), this.Types);

      }
      return base.OnReflecting(module);
    }

    protected override void DisposeList(bool disposing)
    {
      this.DisposeList(this.Fields);
      this.DisposeList(this.Methods);
      this.DisposeList(this.Types);
    }
    protected override void OnBreakingReference()
    {
      this.Break(this.EntryPoint);
      this.Break(this.Fields);
      this.Break(this.Methods);
      this.Break(this.Types);
      base.OnBreakingReference();
    }
    #endregion
    #endregion
  }
}
